package com.huangyi.portalservice.homepage.service.impl;


import com.huangyi.adminapi.config.domain.dto.DictionaryDataDTO;
import com.huangyi.adminapi.config.feign.DictionaryFeignClient;
import com.huangyi.commoncore.utils.JsonUtil;
import com.huangyi.commoncore.utils.StringUtil;
import com.huangyi.commondomain.exception.ServiceException;
import com.huangyi.commonredis.service.RedisService;
import com.huangyi.portalservice.homepage.domain.dto.DictDataDTO;
import com.huangyi.portalservice.homepage.service.IDictionaryService;
import io.micrometer.common.util.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import org.springframework.util.CollectionUtils;
/**
 * 字典服务实现类
 * 
 * <p>提供字典数据的查询和缓存功能，支持批量查询和Redis缓存优化
 * 
 * @author huangyi
 * @version 1.0
 * @since 2024
 */
@Component
@Slf4j
public class DictionaryServiceImpl implements IDictionaryService {

    private static final String DICT_TYPE_PREFIX = "applet:dict:type:";
    private static final Long DICT_TYPE_TIMEOUT = 5L;
    private static final String DICT_DATA_PREFIX = "applet:dict:data:";
    private static final Long DICT_DATA_TIMEOUT = 5L;


    @Autowired
    private DictionaryFeignClient dictionaryFeignClient;
    @Autowired
    private RedisService redisService;

    @Override
    public Map<String, List<DictDataDTO>> batchFindDictionaryDataByTypes(List<String> types) {
        Map<String, List<DictDataDTO>> result = new HashMap<>();
        List<String> notCacheTypes = new ArrayList<>();
        // 遍历分为已有缓存 和 缓存不存在
        for (String type : types) {
            List<DictDataDTO> dataDTOList = getCacheList(type);
            if(CollectionUtils.isEmpty(dataDTOList)) {
                notCacheTypes.add(type);
            }else{
                result.put(type, dataDTOList);
            }
        }

        // 如果都在缓存中存在则直接返回
        if(notCacheTypes.isEmpty()) {
            return result;
        }

        // Redis 中的不存在的types, 去 MySQL 中查
        Map<String, List<DictionaryDataDTO>> listMap = dictionaryFeignClient.selectDictDataByTypes(notCacheTypes);
        if(CollectionUtils.isEmpty(listMap)) {
            log.error("字典类型不存在！notCacheTypes:{}" , JsonUtil.obj2String(notCacheTypes));
            return result;
        }

        // 因为一个 map 里存着多个 type, 而一个 type 又对应多个 data, 所以使用 Map.Entry 遍历, 数据库查到先缓存再设置到返回结果里
        for(Map.Entry<String, List<DictionaryDataDTO>> entry: listMap.entrySet()) {
            List<DictDataDTO> tmpList = entry.getValue().stream()
                            .map(dictionaryDataDTO -> {
                                DictDataDTO dictDataDTO = new DictDataDTO();
                                BeanUtils.copyProperties(dictionaryDataDTO, dictDataDTO);
                                return dictDataDTO;
                            }).collect(Collectors.toList());
            cacheList(entry.getKey(), tmpList);
            result.put(entry.getKey(), tmpList);
        }

        return result;
    }


    private void cacheList(String type, List<DictDataDTO> copyListProperties) {
        if(StringUtils.isBlank(type)) {
            return;
        }
        // 字典服务会传入多个 1 对多的对象列表, 2级城市对3级区县为 1 对多,注意区分
        redisService.setCacheObject(DICT_TYPE_PREFIX + type,
                JsonUtil.obj2String(copyListProperties),
                DICT_TYPE_TIMEOUT, TimeUnit.MINUTES);
    }

    private List<DictDataDTO> getCacheList(String type) {
        if(StringUtils.isBlank(type)) {
            return Arrays.asList();
        }

        // 存进去时存的是字符串, 取出的时候也是字符串
        String str = redisService.getCacheObject(DICT_TYPE_PREFIX + type, String.class);
        if(StringUtils.isBlank(str)) {
            return Arrays.asList();
        }

        return JsonUtil.string2List(str, DictDataDTO.class);
    }

    /**
     * 根据字典数据keys获取字典数据
     *
     * @param dataKeys
     * @return
     */
    @Override
    public Map<String, DictDataDTO> batchFindDictionaryData(List<String> dataKeys) {
        if(CollectionUtils.isEmpty(dataKeys)) {
            log.error("传入的字典数据为空，不能查询字典数据值！");
            return new HashMap<>();
        }

        Map<String, DictDataDTO> result = new HashMap<>();
        List<String> notCacheTypes = new ArrayList<>();
        // 1. 查缓存， 区分已存在缓存的 和 未存在缓存的数据
        for(String dataKey : dataKeys) {
            DictDataDTO dictDataDTO = getDataCache(dataKey);
            if(dictDataDTO == null) {
                notCacheTypes.add(dataKey);
            }else {
                result.put(dataKey, dictDataDTO);
            }
        }

        // 2. 如果 notCacheTypes 说明全部存在
        if(notCacheTypes.isEmpty()) {
            return result;
        }

        // 3. 没在缓存中的调用 openFeign 去 MySQL 查询并设置在返回结果
        List<DictionaryDataDTO> list = dictionaryFeignClient.getDicDataByKeys(notCacheTypes);
        if(CollectionUtils.isEmpty(list)) {
            log.error("feign 字典数据不存在！ notCacheTypes:{}" , JsonUtil.obj2String(notCacheTypes));
            return result;
        }
        for(DictionaryDataDTO dictDataDTO : list) {
            DictDataDTO dictData = new DictDataDTO();
            BeanUtils.copyProperties(dictDataDTO, dictData);
            // 4. 设置缓存
            cacheData(dictData.getDataKey(), dictData);
            result.put(dictData.getDataKey(),dictData);
        }


        // 5. 返回结果
        return result;
    }


    private void cacheData(String dataKey, DictDataDTO dictDataDTO) {
        if(StringUtils.isBlank(dataKey)) {
            return;
        }
        redisService.setCacheObject(DICT_DATA_PREFIX + dataKey,
                JsonUtil.obj2String(dictDataDTO),
                DICT_DATA_TIMEOUT,
                TimeUnit.MINUTES);
    }

    private DictDataDTO getDataCache(String dataKey) {
        if(StringUtils.isBlank(dataKey)) {
            log.error("字典数据为空, 不能获取缓存！");
            return new DictDataDTO();
        }

        // 因为 redisTemplate 的设置原因, 传出的都是 str 类型, 后续才能根据 JsonUtil 进行类型转换
        // 如果不人为控制进入 redis 的类型, 会发生类型擦除等问题
        String str = redisService.getCacheObject(DICT_TYPE_PREFIX + dataKey, String.class);
        if(StringUtils.isBlank(str)) {
            log.error("缓存中未查询到，获取缓存失败！");
            return new DictDataDTO();
        }
        return JsonUtil.string2Obj(str, DictDataDTO.class);
    }
}

